import { profile } from '@/utils/decorators';
import { Logger } from '@/utils/logger';
import { BaseColonyModule } from '../moduleBase';
import { useConfig } from './auto';

const CHAR_BODYPART_MAP = {
    W: WORK,
    C: CARRY,
    M: MOVE,
    A: ATTACK,
    R: RANGED_ATTACK,
    H: HEAL,
    c: CLAIM,
    T: TOUGH
};
const BODYPART_CHAR_MAP = {
    [WORK]: 'W',
    [CARRY]: 'C',
    [MOVE]: 'M',
    [ATTACK]: 'A',
    [RANGED_ATTACK]: 'R',
    [HEAL]: 'H',
    [CLAIM]: 'c',
    [TOUGH]: 'T'
};

const visualizeTask = (spawn: StructureSpawn, role: CreepRole) => {
    const visual = spawn.room.visual;
    visual.text(role, spawn.pos, {
        color: 'cyan',
        font: 0.35,
        align: 'center'
    });
}

/**
 * @see https://www.tddx.net/post-533.html
 * @example
 * Memory.totalCreepCounter = 114514;
 * getCreepName();         // 0001bf52 
 */
const getCreepNameId = () => {
    Memory.totalCreepCounter ??= 0;
    const id = Memory.totalCreepCounter.toString(16);
    return (Array(8).join('0') + id).slice(-8);
}


export function parseBodyString(str: string) {
    const body: BodyPartConstant[] = [];
    for (let i = 0; i < str.length; ++i) {
        body.push(CHAR_BODYPART_MAP[str[i]]);
    }
    return body;
}

export function serializeBody(body: BodyPartConstant[]) {
    let str = '';
    body.forEach(bp => str += BODYPART_CHAR_MAP[bp]);
    return str;
}

/**
 * @param bodyTable 体型配置数组
 * @param energyAvailable 最多可以用于孵化的能量，通常为 `Room.energyAvailable`
 * @param targetLevel 房间当前等级，即使能量足够孵化更高等级的爬也不会使用高等级的配置
 * @returns 压缩后的身体部件字符串，使用 `parseBodyString` 解压
 */
export function getBodyParts(bodyTable: BodyTable, energyAvailable: number, targetLevel = 8) {
    // 从大到小选择
    for (let i = bodyTable.length - 1; i >= 0; --i) {
        // 计算该种方式要花费多少能量
        const components = bodyTable[i];
        let totalEnergy = 0;
        components.forEach(pair => {
            const [part, num] = pair;
            totalEnergy += BODYPART_COST[part] * num;
        });
        if ((totalEnergy <= energyAvailable && i <= targetLevel - 1) || i === 0) {
            // 找到了一种可行的方案，把它压缩成字符串
            let str = '';
            components.forEach(pair => {
                const [part, num] = pair;
                for (let i = 0; i < num; ++i) {
                    str += BODYPART_CHAR_MAP[part];
                }
            });
            return str;
        }
    }
    return '';
}

export class SpawnModule extends BaseColonyModule {

    request(data: SpawnRequest) {
        if (this.memory.spawnList) {
            this.memory.spawnList.push(data);
        }
        else {
            this.memory.spawnList = [data];
        }
    }

    private getSpawnDirection(index: number, isFastFiller = false) {
        if (index === 0) {
            return isFastFiller ? [BOTTOM_RIGHT] :
                [TOP, TOP_LEFT, TOP_RIGHT, LEFT, BOTTOM_LEFT];
        }
        else if (index === 1) {
            return isFastFiller ? [TOP_LEFT, TOP_RIGHT] :
                [BOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT];
        }
        else if (index === 3) {
            return isFastFiller ? [BOTTOM_LEFT] :
                [TOP, TOP_RIGHT, TOP_LEFT, RIGHT, BOTTOM_RIGHT];
        }
        else {
            return [];
        }
    }

    /**
     * 使用指定的 spawn 运行 spawnCreep 任务
     * @param spawn 要用来产爬的 spawn
     * @param index 序号，对于每个 spawn 来说产出的方向不同，从左到右 0-2
     */
    @profile
    private runSpawn(spawn: StructureSpawn, index: number, fastfillerSlot = false) {
        const { spawnList } = this.memory;
        // 检查队列中的任务
        if (!spawnList || spawnList.length === 0) {
            return;
        }
        // 根据优先级排序
        this.memory.spawnList.sort((a, b) => b.priority - a.priority);
        // 解析部件
        const body = this.parseBodyString(spawnList[0].body);
        // 解析内存
        const memory = { ...spawnList[0].data };
        memory.role = spawnList[0].role;
        memory.home = this.colony.name;
        memory.onBorn = true;
        // 获取名字，如果没有指定就随即取名
        const name = (spawnList[0].name ?? '0x') + getCreepNameId();
        // spawnCreep
        const code = spawn.spawnCreep(body, name, {
            memory,
            directions: this.getSpawnDirection(index, fastfillerSlot)
        });
        // 检查返回值
        if (code === OK) {
            spawnList.shift();
            Game.hasCreepNumChanged = true; // 下一 tick 刷新数据
            Memory.totalCreepCounter++;
        }
        else if (code === ERR_NAME_EXISTS) {
            Logger.warn(`Spawn 爬 "${name}" 时失败，原因：重名`, 'spawn');
            spawnList.shift();
        }
        else if (code === ERR_NOT_ENOUGH_ENERGY) {
            this.suspendTask();
        }
        else {
            Logger.warn(`Spawn 爬 "${name}" 时失败（❌=${code} | ${spawn.pos.roomName}）`, 'spawn');
        }
    }

    /**
     * 挂起当前任务，将任务移到队伍尾端
     */
    suspendTask() {
        const { spawnList } = this.memory;
        if (spawnList.length === 1) return;
        const request = spawnList.shift();
        spawnList.push(request);
    }

    recreateCreep(creep: Creep, keepTarget = false) {
        const request = useConfig(
            creep.getRole(),
            this.colony.capital.energyAvailable,
            this.colony.level,
        );
        if (keepTarget) {
            request.data = {
                target: creep.memory.target
            };
        }
        this.request(request);
    }

    parseBodyString = parseBodyString;

    serializeBody = serializeBody;

    getBodyParts = getBodyParts;

    handle() {
        // 检查是否有活干
        if (!this.memory.spawnList?.[0]) {
            return;
        }
        // 找一个没有活干的 spawn
        const spawn = this.colony.capital.getSpawns().find(spawn => !spawn.spawning);
        if (spawn) {
            // 每一个 tick 只生产一个爬，避免因为 energyAvailable 不同步减少
            // 而导致所需能量过多，生产效率低下的问题
            this.runSpawn(spawn, spawn.memory.i ?? 0);
        }
    }

}
